﻿
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using XNADota.ContentPipeline.MDX;
using System;
using XNADota.Primitive;
using XNADota.Graphics;
using System.Diagnostics;
using XNADota.GamePlay;

namespace XNADota.MDX
{
    public static class ReplaceableTextures
    {
        public static void Initialize(ContentManager ContentManager)
        {
            Red = ContentManager.Load<Texture2D>(TEXTURE_REPLACABLE_RED);
            Cliff0 = ContentManager.Load<Texture2D>(REPLACABLE_TEXTURE_CLIFF0);
            Cliff1 = ContentManager.Load<Texture2D>(REPLACABLE_TEXTURE_CLIFF1);
        }
        private const string TEXTURE_REPLACABLE_RED = "Textures\\ReplacableRed";
        private const string REPLACABLE_TEXTURE_CLIFF0 = "ReplaceableTextures\\Cliff\\C_Cliff0";
        private const string REPLACABLE_TEXTURE_CLIFF1 = "ReplaceableTextures\\Cliff\\C_Cliff1";

        public static Texture2D Red;
        public static Texture2D Cliff0;
        public static Texture2D Cliff1;
    }

    public class ModelRenderer : PaletteRenderer
    {
        protected GraphicsDevice _GraphicsDevice;
        protected Model _Model;
        protected List<GeosetRenderer> _GeosetRenderers;
        private List<NodeRenderer> _NodeRenderers;  // bones, helpers, attachments, collisionShapes, lights, camera
        private List<int> _TopologicalIndex;
        private List<GeosetAnimationRenderer> _GeosetAnimationRenderers;
        private SequenceAnimationPlayer _SequenceAnimationPlayer;
        private CollisionShapeDectector _CollisionShapeDectector;

        public SequenceAnimationPlayer Player
        {
            get { return _SequenceAnimationPlayer; }
        }

        // constructor
        public Model Model
        {
            set { _Model = value; }
            get { return _Model; }
        }

        public ModelRenderer(GraphicsDevice graphicsDevice, Model Model)
        {
            // allow null object
            if (graphicsDevice == null || Model == null)
                return;

            // validate the correction of the model
            try
            {
                if (Model.Validate() == false)
                    throw new InvalidModelException("Model is invalid.");
            }
            catch (System.Exception e)
            {

                throw new InvalidModelException("Model is invalid", e);
            }

            // build geoset related data
            this._GraphicsDevice = graphicsDevice;
            this._Model = Model;
            this._GeosetRenderers = new List<GeosetRenderer>();

            foreach (Geoset geoset in _Model.Geosets)
            {
                this._GeosetRenderers.Add(new GeosetRenderer(graphicsDevice, geoset, this));
            }

            // build bones
            InitializeBonesAndHelpersAndAnimationRenderersAndAttachmentsAndCollisionShapesAndLightsAndCameras();

            // build sequences
            _SequenceAnimationPlayer = new SequenceAnimationPlayer(_Model.Sequences);

            // build collision shape detector
            _CollisionShapeDectector = new CollisionShapeDectector(_Model.CollisionShapes);
        }

        private bool[] _Visited;
        private void InitializeBonesAndHelpersAndAnimationRenderersAndAttachmentsAndCollisionShapesAndLightsAndCameras()
        {
            _GeosetAnimationRenderers = new List<GeosetAnimationRenderer>();
            _GeosetAnimationRenderers.Clear();
            for (int i = 0; i < _Model.GeosetAnimations.Count; i++)
                _GeosetAnimationRenderers.Add(new GeosetAnimationRenderer(_Model.GeosetAnimations[i], _GeosetRenderers));

            _NodeRenderers = new List<NodeRenderer>();
            _NodeRenderers.Clear();

            for (int i = 0; i < _Model.Bones.Count; i++)
                _NodeRenderers.Add(new BoneRenderer(_Model.Bones[i], this._GeosetAnimationRenderers));

            for (int i = 0; i < _Model.Lights.Count; i++)
                _NodeRenderers.Add(new LightRenderer(_Model.Lights[i]));

            for (int i = 0; i < _Model.Helpers.Count; i++)
                _NodeRenderers.Add(new HelperRenderer(_Model.Helpers[i]));

            for (int i = 0; i < _Model.Attachments.Count; i++)
                _NodeRenderers.Add(new AttachmentRenderer(_Model.Attachments[i]));

            for (int i = 0; i < _Model.ParticleEmitters.Count; i++)
                _NodeRenderers.Add(new ParticleEmitterRenderer(_Model.ParticleEmitters[i]));

            for (int i = 0; i < _Model.ParticleEmitter2s.Count; i++)
                _NodeRenderers.Add(new ParticleEmitter2Renderer(_Model.ParticleEmitter2s[i]));

            for (int i = 0; i < _Model.RibbonEmitters.Count; i++)
                _NodeRenderers.Add(new RibbonEmitterRenderer(_Model.RibbonEmitters[i]));

            for (int i = 0; i < _Model.EventObjects.Count; i++)
                _NodeRenderers.Add(new EventObjectRenderer(_Model.EventObjects[i]));

            for (int i = 0; i < _Model.CollisionShapes.Count; i++)
                _NodeRenderers.Add(new CollisionShapeRenderer(_Model.CollisionShapes[i]));

            for (int i = 0; i < _NodeRenderers.Count; i++)
            {
                if (_NodeRenderers[i].NodeId != i)
                    throw new InvalidModelException("Model format is incorrect.");
            }

            // clear topological index array
            _TopologicalIndex = new List<int>();

            // clear visited array
            _Visited = new bool[_Model.Bones.Count + _Model.Lights.Count + _Model.Helpers.Count +
                                _Model.ParticleEmitters.Count + _Model.ParticleEmitter2s.Count +
                                _Model.RibbonEmitters.Count + _Model.EventObjects.Count +
                                _Model.Attachments.Count + _Model.CollisionShapes.Count];
            for (int i = 0; i < _Model.Bones.Count + Model.Helpers.Count; i++)
                _Visited[i] = false;

            // start topological
            for (int i = 0; i < _NodeRenderers.Count; i++)
            {
                TopologicalSort(_NodeRenderers[i]);
            }

            if (_TopologicalIndex.Count != _NodeRenderers.Count)
                throw new InvalidModelException("Count of topological index is incorrect.");

            _Visited = null;
        }
        private void TopologicalSort(NodeRenderer nodeRenderer)
        {
            // this is not all correct, because attachment and helper, what are added later, is not calculated.
            if (nodeRenderer.NodeId >= _Visited.Length || nodeRenderer.NodeId < 0)
            {
                throw new InvalidModelException("NodeRenderer index is out of range");
                //return;
            }

            if (_Visited[nodeRenderer.NodeId])
                return;
            else
            {
                if (nodeRenderer.ParentNodeId != -1)
                    TopologicalSort(_NodeRenderers[nodeRenderer.ParentNodeId]);

                _TopologicalIndex.Add(nodeRenderer.NodeId);
                _Visited[nodeRenderer.NodeId] = true;
            }
        }

        public void Update(GameTime time)
        {
            // this fragment is not used anymore
            //throw new Exception("Don't invoke it.");

            _SequenceAnimationPlayer.Update(time.ElapsedGameTime);
            int currentTick = _SequenceAnimationPlayer.CurrentTick;
            if (currentTick >= 0)
                UpdateAnimationTick(currentTick);
        }

        public void UpdateAnimationTick(int currentTick)
        {
            // update bone renderers
            for (int nodeIndex = 0; nodeIndex < _NodeRenderers.Count; nodeIndex++)
            {
                _NodeRenderers[nodeIndex].Update(currentTick);
            }

            // update world matrix
            for (int i = 0; i < _NodeRenderers.Count; i++)
            {
                // get current and parent index
                int current = _TopologicalIndex[i];
                int parent = _NodeRenderers[current].ParentNodeId;

                if (parent != -1)
                {
                    _NodeRenderers[current].World = _NodeRenderers[current].World * _NodeRenderers[parent].World;
                }

                _NodeRenderers[current].Draw();
            }

            // update geoset, cpu skinning
            foreach (GeosetRenderer GeosetRenderer in _GeosetRenderers)
            {
                GeosetRenderer.NodeRenderer = _NodeRenderers;
                GeosetRenderer.Update();
            }

            foreach (GeosetAnimationRenderer GeosetAnimationRenderer in _GeosetAnimationRenderers)
            {
                GeosetAnimationRenderer.Update(currentTick);
            }
        }

        public void UpdateRelpacableTexture(Texture2D texture)
        {
            foreach (GeosetRenderer GeosetRenderer in _GeosetRenderers)
            {
                foreach (LayerRenderer layerRenderer in GeosetRenderer.MaterialRenderer.LayerRenderers)
                {
                    layerRenderer.TextureRenderer.ReplaceReplaceableTexture(texture);
                }
            }
        }

        /// <summary>
        /// Draws the model using the specified camera matrices.
        /// </summary>
        public void Draw(Matrix world, Matrix view, Matrix projection)
        {
            foreach (GeosetRenderer GeosetRenderer in _GeosetRenderers)
            {
                GeosetRenderer.Draw(world, view, projection);
            }
        }
    }

    public class GeosetRenderer : RenderUnit
    {
        private Geoset _Geoset;

        private MaterialRenderer _MaterialRenderer;

        private Effect _Effect;
        private ERenderState _RenderStates;

        private float _Alpha;

        public float Alpha
        {
            get { return _Alpha; }
            set { _Alpha = Math.Max(Math.Min(value, 1), 0); }
        }

        private List<NodeRenderer> _NodeRenderers;

        public List<NodeRenderer> NodeRenderer
        {
            set { _NodeRenderers = value; }
        }

        public MaterialRenderer MaterialRenderer
        {
            get { return _MaterialRenderer; }
        }

        /// <summary>
        /// Once all the geometry has been specified by calling AddVertex and AddIndex,
        /// this method copies the vertex and index data into GPU format buffers, ready
        /// for efficient rendering.
        public GeosetRenderer(GraphicsDevice graphicsDevice, Geoset Geoset, ModelRenderer ModelRenderer)
        {
            this._Alpha = 1.0f;
            this._Geoset = Geoset;

            this._MaterialRenderer = new MaterialRenderer(ModelRenderer, Geoset.MaterialID);

            // Create a vertex declaration, describing the format of our vertex data.
            VertexDeclaration = new VertexDeclaration(graphicsDevice,
                                                VertexPositionNormalTexture.VertexElements);

            // Create a vertex buffer, and copy our vertex data into it.
            DynamicVertexBuffer = new DynamicVertexBuffer(graphicsDevice,
                                            typeof(VertexPositionNormalTexture),
                                            _Geoset.Vertices.Length, BufferUsage.None);

            DynamicVertexBuffer.SetData(_Geoset.Vertices);

            // Create an index buffer, and copy our index data into it.
            IndexBuffer = new IndexBuffer(graphicsDevice, typeof(int),
                                          _Geoset.Indices.Length, BufferUsage.None);

            IndexBuffer.SetData(_Geoset.Indices);

            PaletteRendererManager manager = SceneManager.Instance.PaletteRendererManager;

            // Create a BasicEffect, which will be used to render the primitive.
            _Effect = new BasicEffect(graphicsDevice, null);
            _Effect = SceneManager.Instance.GetEffect(_Effect);

            Graphics.Techniques.Material material = new Graphics.Techniques.Material();
            material = SceneManager.Instance.GetMaterial(material);
            _MaterialRenderer.Material = material;

            _RenderStates = ERenderState.Solid | ERenderState.EnableDepth | ERenderState.CullClockWise;

            this.TriangleCount = _Geoset.TriangleCount;
            this.VerticesCount = _Geoset.VerticesCount;
        }

        public void Update()
        {
            VertexPositionNormalTexture[] VerticesCopy = (VertexPositionNormalTexture[])_Geoset.Vertices.Clone();
            // cpu calculation of vertex position
            for (int vertexIndex = 0; vertexIndex < _Geoset.Vertices.Length; vertexIndex++)
            {
                int vertexGroupID = _Geoset.VertexGroupID[vertexIndex];
                List<int> groups = _Geoset.GeosetGroups[vertexGroupID];

                Matrix matrix = new Matrix();
                for (int groupIndex = 0; groupIndex < groups.Count; groupIndex++)
                {
                    int nodeIndex = groups[groupIndex];
                    matrix += _NodeRenderers[nodeIndex].World / (float)groups.Count;
                }

                VerticesCopy[vertexIndex].Position = Vector3.Transform(VerticesCopy[vertexIndex].Position, matrix);
            }
            DynamicVertexBuffer.SetData(VerticesCopy, 0, VerticesCopy.Length, SetDataOptions.Discard);
        }

        private enum EChoose : int
        {
            NoProper = 0,
            ChooseA = 1,
            ChooseB = 2,
            ChooseNone = 3,
        }

        private EChoose OnEdge(Vector3 PointA, Vector3 TestPoint, Vector3 PointB)
        {
            const float nearDis = 0.1f;
            EChoose result = EChoose.NoProper;
            if ((Math.Abs(PointA.X - PointB.X) < nearDis && Math.Abs(TestPoint.X - PointA.X) < nearDis) ||
                (Math.Abs(PointA.Y - PointB.Y) < nearDis && Math.Abs(TestPoint.Y - PointA.Y) < nearDis))
            {
                result = EChoose.ChooseNone;
                if (Math.Abs(TestPoint.Z - PointA.Z) < 64.0f) result = EChoose.ChooseA;
                if (Math.Abs(TestPoint.Z - PointB.Z) < 64.0f) result = EChoose.ChooseB;
            }
            return result;
        }

        public void UpdateCliff(Vector3[] Positions, bool IsCliffTrans, EDirection Direction, ESide side, Matrix World)
        {
            // Actually, this cpu calculating should be separated from geoset renderer, as a part of Cliff renderer, which can not be done now.
            // It will be imported into an .fx file, and this can be ported into the cliff renderer

            // Logics
            // 1. Get Original Six Points Of Cliffs
            // 2. Get Transformed Six Points Of Cliffs
            // 3. Put Original points to transformed points, other points on edges will be set height by choosing Z near point

            // Validate the correction
            Debug.Assert(Positions.Length == 6);

            // The code is dirty, and will be changed placed or written in the .fx files
            VertexPositionNormalTexture[] VerticesCopy = (VertexPositionNormalTexture[])_Geoset.Vertices.Clone();

            int Left = -128;
            int Top = 128;
            int Right = 0;
            int Bottom = 0;

            if (IsCliffTrans)
            {
                switch (Direction)
                {
                    case EDirection.North:
                        Top = 256;
                        break;
                    case EDirection.South:
                        Top = 256;
                        break;
                    case EDirection.West:
                        Left = -256;
                        break;
                    case EDirection.East:
                        Left = -256;
                        break;
                }
            }

            Vector3 AA, BB, CC, DD, EE, FF;
            AA = BB = CC = DD = EE = FF = new Vector3();
            for (int vertexIndex = 0; vertexIndex < _Geoset.Vertices.Length; vertexIndex++)
            {
                Vector3 position = _Geoset.Vertices[vertexIndex].Position;

                // (Left, Top) ----- (Right, Top)
                //      |       T      |
                //      |L            R|
                //      |       B      |
                // (Left, Bottom)  ----- (Right, Bottom)
                var A = new Vector2(Left, Bottom);
                var B = new Vector2(Left, Top);
                var C = new Vector2(Right, Top);
                var D = new Vector2(Right, Bottom);
                Vector2 E = new Vector2();
                Vector2 F = new Vector2();

                if (IsCliffTrans)
                {
                    switch (Direction)
                    {
                        case EDirection.North:
                            E = new Vector2(Left, (Top + Bottom) / 2);
                            F = new Vector2(Right, (Top + Bottom) / 2);
                            break;
                        case EDirection.South:
                            E = new Vector2(Left, (Top + Bottom) / 2);
                            F = new Vector2(Right, (Top + Bottom) / 2);
                            break;
                        case EDirection.West:
                            E = new Vector2((Left + Right) / 2, Top);
                            F = new Vector2((Left + Right) / 2, Bottom);
                            break;
                        case EDirection.East:
                            E = new Vector2((Left + Right) / 2, Top);
                            F = new Vector2((Left + Right) / 2, Bottom);
                            break;
                    }
                }

                Vector2 testPoint = new Vector2(position.X, position.Y);
                bool b0 = Vector2.Distance(A, testPoint) < 0.1f;// position.X == Left && position.Y == Bottom;
                bool b1 = Vector2.Distance(B, testPoint) < 0.1f;// position.X == Left && position.Y == Top;
                bool b2 = Vector2.Distance(C, testPoint) < 0.1f; // position.X == Right && position.Y == Top;
                bool b3 = Vector2.Distance(D, testPoint) < 0.1f; // position.X == Right && position.Y == Bottom;
                bool b4 = false;
                bool b5 = false;
                if (IsCliffTrans)
                {
                    b4 = Vector2.Distance(E, testPoint) < 0.1f;
                    b5 = Vector2.Distance(F, testPoint) < 0.1f;
                }

                if (b0) { AA = position; }
                if (b1) { BB = position; }
                if (b2) { CC = position; }
                if (b3) { DD = position; }
                if (IsCliffTrans)
                {
                    if (b4) { EE = position; }
                    if (b5) { FF = position; }
                }
            }

            for (int vertexIndex = 0; vertexIndex < _Geoset.Vertices.Length; vertexIndex++)
            {
                Vector3 position = _Geoset.Vertices[vertexIndex].Position;
                Vector3 final = Vector3.Transform(_Geoset.Vertices[vertexIndex].Position, World);

                bool b0, b1, b2, b3;
                b0 = b1 = b2 = b3 = false;

                const float cmpEdge = 0.1f;
                const float cmp = 64.0f; ;

                // 0 is get around, 1 is lerp
                int strategy = 0;
                char edge = '0'; // L is L, T is T, R is R, B is B

                if (Math.Abs(position.X - Left) < cmpEdge) edge = 'L';
                if (Math.Abs(position.X - Right) < cmpEdge) edge = 'R';
                if (Math.Abs(position.Y - Bottom) < cmpEdge) edge = 'B';
                if (Math.Abs(position.Y - Top) < cmpEdge) edge = 'T';

                if (IsCliffTrans)
                {
                    switch (Direction)
                    {
                        case EDirection.North:
                            if (edge == 'L' && side == ESide.Left) { strategy = 1; }
                            if (edge == 'R' && side == ESide.Right) { strategy = 1; }
                            break;
                        case EDirection.South:
                            if (edge == 'R' && side == ESide.Left) { strategy = 1; }
                            if (edge == 'L' && side == ESide.Right) { strategy = 1; }
                            break;
                        case EDirection.West:
                            if (edge == 'B' && side == ESide.Left) { strategy = 1; }
                            if (edge == 'T' && side == ESide.Right) { strategy = 1; }
                            Left = -256;
                            break;
                        case EDirection.East:
                            if (edge == 'T' && side == ESide.Left) { strategy = 1; }
                            if (edge == 'B' && side == ESide.Right) { strategy = 1; }
                            Left = -256;
                            break;
                    }
                }

                if (strategy == 0)
                {
                    if (IsCliffTrans == false)
                    {
                        //  BB -- CC   1 -- 2
                        //   |     |   |    |
                        //  AA -- DD   0 -- 3
                        EChoose choose;
                        choose = OnEdge(AA, position, BB);
                        if (choose == EChoose.ChooseA) final.Y = Positions[0].Y;
                        if (choose == EChoose.ChooseB) final.Y = Positions[1].Y;
                        if (choose == EChoose.ChooseNone) final = Vector3.Lerp(Positions[0], Positions[1], 0.5f);

                        choose = OnEdge(BB, position, CC);
                        if (choose == EChoose.ChooseA) final.Y = Positions[1].Y;
                        if (choose == EChoose.ChooseB) final.Y = Positions[2].Y;
                        if (choose == EChoose.ChooseNone) final = Vector3.Lerp(Positions[1], Positions[2], 0.5f);

                        choose = OnEdge(CC, position, DD);
                        if (choose == EChoose.ChooseA) final.Y = Positions[2].Y;
                        if (choose == EChoose.ChooseB) final.Y = Positions[3].Y;
                        if (choose == EChoose.ChooseNone) final = Vector3.Lerp(Positions[2], Positions[3], 0.5f);

                        choose = OnEdge(DD, position, AA);
                        if (choose == EChoose.ChooseA) final.Y = Positions[3].Y;
                        if (choose == EChoose.ChooseB) final.Y = Positions[0].Y;
                        if (choose == EChoose.ChooseNone) final = Vector3.Lerp(Positions[3], Positions[0], 0.5f);
                    }
                    else
                    {
                        if (Direction == EDirection.West || Direction == EDirection.East)
                        {
                            //  BB -- EE -- CC   1 -- 4 -- 2
                            //   |           |   |         |
                            //  AA -- FF -- DD   0 -- 5 -- 3
                            EChoose choose;
                            choose = OnEdge(AA, position, BB);
                            if (choose == EChoose.ChooseA) final.Y = Positions[0].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[1].Y;

                            choose = OnEdge(BB, position, EE);
                            if (choose == EChoose.ChooseA) final.Y = Positions[1].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[4].Y;

                            choose = OnEdge(EE, position, CC);
                            if (choose == EChoose.ChooseA) final.Y = Positions[4].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[2].Y;

                            choose = OnEdge(CC, position, DD);
                            if (choose == EChoose.ChooseA) final.Y = Positions[2].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[3].Y;

                            choose = OnEdge(DD, position, FF);
                            if (choose == EChoose.ChooseA) final.Y = Positions[3].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[5].Y;

                            choose = OnEdge(FF, position, AA);
                            if (choose == EChoose.ChooseA) final.Y = Positions[5].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[0].Y;
                        }
                        else
                        {
                            //  BB -- CC  1 -- 2
                            //   |     |  |    |
                            //  EE    FF  4    5
                            //   |     |  |    |
                            //  AA -- DD  0 -- 3
                            EChoose choose;
                            choose = OnEdge(AA, position, EE);
                            if (choose == EChoose.ChooseA) final.Y = Positions[0].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[4].Y;

                            choose = OnEdge(EE, position, BB);
                            if (choose == EChoose.ChooseA) final.Y = Positions[4].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[1].Y;

                            choose = OnEdge(BB, position, CC);
                            if (choose == EChoose.ChooseA) final.Y = Positions[1].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[2].Y;

                            choose = OnEdge(CC, position, FF);
                            if (choose == EChoose.ChooseA) final.Y = Positions[2].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[5].Y;

                            choose = OnEdge(FF, position, DD);
                            if (choose == EChoose.ChooseA) final.Y = Positions[5].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[3].Y;

                            choose = OnEdge(DD, position, AA);
                            if (choose == EChoose.ChooseA) final.Y = Positions[3].Y;
                            if (choose == EChoose.ChooseB) final.Y = Positions[0].Y;
                        }

                    }

                    if (IsCliffTrans == false && edge == '0')
                    {
                        float maxH = Math.Max(Math.Max(Positions[0].Y, Positions[1].Y), Math.Max(Positions[2].Y, Positions[3].Y));
                        float minH = Math.Min(Math.Min(Positions[0].Y, Positions[1].Y), Math.Min(Positions[2].Y, Positions[3].Y));
                        float halfH = (maxH + minH) / 2.0f;

                        float maxZ = Math.Max(Math.Max(AA.Z, BB.Z), Math.Max(CC.Z, DD.Z));
                        float minZ = Math.Min(Math.Min(AA.Z, BB.Z), Math.Min(CC.Z, DD.Z));
                        float halfZ = (maxZ + minZ) / 2.0f;
                        if (maxZ - position.Z < cmp)
                            final.Y = maxH;
                        else
                            if (position.Z - minZ < cmp)
                                final.Y = minH;
                            else
                                final.Y = halfH;
                    }

                    //if (edge != '0' && IsCliffTrans)
                    //    DebugPrimitiveRenderer.AddImmdiatePrimitive(final, Color.LightPink);
                }
                else
                {
                    float amount = 0;
                    switch (edge)
                    {
                        case 'L':
                            amount = (position.Y - Bottom) / (float)(Top - Bottom);
                            final = Vector3.Lerp(Positions[0], Positions[1], amount);
                            break;
                        case 'R':
                            amount = (position.Y - Bottom) / (float)(Top - Bottom);
                            final = Vector3.Lerp(Positions[3], Positions[2], amount);
                            break;
                        case 'T':
                            amount = (position.X - Left) / (float)(Right - Left);
                            final = Vector3.Lerp(Positions[1], Positions[2], amount);
                            break;
                        case 'B':
                            amount = (position.X - Left) / (float)(Right - Left);
                            final = Vector3.Lerp(Positions[0], Positions[3], amount);
                            break;
                    }

                    //if (edge != '0')
                    //    DebugPrimitiveRenderer.AddImmdiatePrimitive(final, Color.LightPink);
                }


                if (position == AA) { final = Positions[0]; }
                if (position == BB) { final = Positions[1]; }
                if (position == CC) { final = Positions[2]; }
                if (position == DD) { final = Positions[3]; }
                if (IsCliffTrans)
                {
                    if (position == EE) { final = Positions[4]; }
                    if (position == FF) { final = Positions[5]; }
                }

                VerticesCopy[vertexIndex].Position = final;
                VerticesCopy[vertexIndex].Normal = Vector3.Transform(_Geoset.Vertices[vertexIndex].Normal, World);

                // push height data
                GameplayWorld.Instance.PathManager.PushHeightData(final, true);
            }

            DynamicVertexBuffer.SetData(VerticesCopy, 0, VerticesCopy.Length, SetDataOptions.Discard);
        }

        /// <summary>
        /// Draws the model using the specified camera matrices.
        /// </summary>
        public void Draw(Matrix world, Matrix view, Matrix projection)
        {
            if (this._Alpha == 0.0f) return;

            // Look up the effect, and set effect parameters on it. This sample
            // assumes the model will only be using BasicEffect, but a more robust
            // implementation would probably want to handle custom effects as well.

            // Set the graphics device to use our vertex declaration, vertex buffer, and index buffer.
            //effect.GraphicsDevice.VertexDeclaration = this.VertexDeclaration;
            //effect.GraphicsDevice.Vertices[0].SetSource(this.DynamicVertexBuffer, 0,
            //                                     VertexPositionNormalTexture.SizeInBytes);
            //effect.GraphicsDevice.Indices = this.IndexBuffer;

            Graphics.Techniques.Material material = this._MaterialRenderer.Material;
            material.View = view;
            material.Projection = projection;

            var layerCount = this._MaterialRenderer.LayerRenderers.Length;
            for (var layerIndex = 0; layerIndex < layerCount; layerIndex++)
            {
                Graphics.Techniques.Layer layer = new Graphics.Techniques.Layer();
                layer.RenderStates = _MaterialRenderer.LayerRenderers[layerIndex].AlphaBlendRenderState | _RenderStates;
                layer.EnableTexture = true;
                layer.Texture = this._MaterialRenderer.LayerRenderers[layerIndex].TextureRenderer.Texture;
                this.World = world;

                SceneManager.Instance.AddRenderUnit(this, _Effect, material, layer);
            }
            //effect.Texture = this._MaterialRenderer.LayerRenderers[layerIndex].TextureRenderer.Texture;

            //RenderStateManager.Instance.SetRenderStates(
            //    _MaterialRenderer.LayerRenderers[layerIndex].AlphaBlendRenderState | _RenderStates);

            //// Begin the effect, and loop over all the effect passes.
            //effect.Begin();

            //foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            //{
            //    pass.Begin();

            //    // Draw the geometry.
            //    effect.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,
            //                                 0, 0, _Geoset.VerticesCount,
            //                                 0, _Geoset.TriangleCount);

            //    pass.End();
            //}

            //effect.End();
        }
    }

    public class MaterialRenderer
    {
        private Material _Material;
        private LayerRenderer[] _LayerRenderers;

        public Graphics.Techniques.Material Material;


        public LayerRenderer[] LayerRenderers
        {
            get { return _LayerRenderers; }
        }

        public MaterialRenderer(ModelRenderer ModelRenderer, int MaterialID)
        {
            if (MaterialID < 0 || MaterialID > ModelRenderer.Model.Materials.Count)
                throw new InvalidModelException("Material ID is out of range, while constructing material renderer.");

            this._Material = ModelRenderer.Model.Materials[MaterialID];

            this._Material = ModelRenderer.Model.Materials[MaterialID];
            this._LayerRenderers = new LayerRenderer[_Material.Layers.Length];
            for (int layerIndex = 0; layerIndex < _Material.Layers.Length; layerIndex++)
            {
                this._LayerRenderers[layerIndex] = new LayerRenderer(ModelRenderer, _Material.Layers[layerIndex]);
            }
        }
    }

    public class LayerRenderer
    {
        private Layer _Layer;
        private TextureRenderer _TextureRenderer;
        private ERenderState _AlphaBlendRenderStates;

        public ERenderState AlphaBlendRenderState
        {
            get { return _AlphaBlendRenderStates; }
            set { _AlphaBlendRenderStates = value; }
        }

        public TextureRenderer TextureRenderer
        {
            get { return _TextureRenderer; }
        }

        public LayerRenderer(ModelRenderer ModelRenderer, Layer Layer)
        {
            _Layer = Layer;

            this._TextureRenderer = new TextureRenderer(ModelRenderer, Layer.CellTextureId);

            switch (_Layer.FilterMode)
            {
                case EMdxMaterialLayerFilterMode.Transparent:
                    _AlphaBlendRenderStates = ERenderState.Translucent;
                    break;
                case EMdxMaterialLayerFilterMode.Blend:
                    _AlphaBlendRenderStates = ERenderState.Transparent;
                    break;
                case EMdxMaterialLayerFilterMode.Additive:
                    _AlphaBlendRenderStates = ERenderState.Additive;
                    break;
                case EMdxMaterialLayerFilterMode.None:
                    _AlphaBlendRenderStates = ERenderState.Opaque;
                    break;
                case EMdxMaterialLayerFilterMode.AdditiveAlpha:
                    _AlphaBlendRenderStates = ERenderState.AdditiveAlpha;
                    break;
                default:
                    _AlphaBlendRenderStates = ERenderState.Translucent;
                    break;
            }
        }
    }

    public class TextureRenderer
    {
        private MdxTexture _MdxTexture;
        private Texture2D _Texture2D;

        public Texture2D Texture
        {
            get { return _Texture2D; }
        }

        public TextureRenderer(ModelRenderer ModelRenderer, int CellTextureId)
        {
            if (CellTextureId < 0 || CellTextureId > ModelRenderer.Model.Textures.Count)
                throw new InvalidModelException("Texture ID is out of range, while constructing layer renderer.");

            this._MdxTexture = ModelRenderer.Model.Textures[CellTextureId];

            if (this._MdxTexture.Texture2D == null)
            {
                ReplaceReplaceableTexture(null);
            }
            else
            {
                this._Texture2D = this._MdxTexture.Texture2D as Texture2D;
            }
        }

        public void ReplaceReplaceableTexture(Texture2D texture)
        {
            if (this._MdxTexture.Texture2D == null)
            {
                if (texture != null)
                    this._Texture2D = texture;
                else
                {
                    switch (this._MdxTexture.ReplaceableId)
                    {
                        case 11:
                            this._Texture2D = ReplaceableTextures.Cliff1;
                            break;
                        default:
                            this._Texture2D = ReplaceableTextures.Red;
                            break;
                    }
                }
            }
        }
    }

    public class GeosetAnimationRenderer
    {
        private GeosetAnimation _GeosetAnimation;
        private GeosetRenderer _GeosetRenderer;

        public GeosetAnimationRenderer(GeosetAnimation GeosetAnimation, List<GeosetRenderer> GeosetRenderers)
        {
            this._GeosetAnimation = GeosetAnimation;

            if (GeosetAnimation.GeosetId >= 0)
                this._GeosetRenderer = GeosetRenderers[GeosetAnimation.GeosetId];
            else
                this._GeosetRenderer = null;
        }

        public void Update(int CurrentTick)
        {
            if (this._GeosetRenderer != null)
                _GeosetRenderer.Alpha = GetCurrentAlpha(CurrentTick);
            else
                _GeosetRenderer.Alpha = 1.0f;
        }

        private float GetCurrentAlpha(int CurrentTick)
        {
            float ret = 1.0f;

            FloatAnimatorNode[] nodes = _GeosetAnimation.AlphaAnimator.AlphaAnimatorNodes;

            int current = -1;
            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i].Time <= CurrentTick && (i + 1 == nodes.Length || CurrentTick < nodes[i + 1].Time))
                {
                    current = i;
                    break;
                }
            }

            if (current != -1)
            {
                float amount = 0;
                if (_GeosetAnimation.AlphaAnimator.Type != EMdxInterpolationType.None && current + 1 < nodes.Length)
                {
                    amount = (CurrentTick - nodes[current].Time) / (nodes[current + 1].Time - nodes[current].Time);
                }
                switch (_GeosetAnimation.AlphaAnimator.Type)
                {
                    case EMdxInterpolationType.None:
                        ret = nodes[current].Value;
                        break;
                    case EMdxInterpolationType.Linear:
                        ret = nodes[current].Value * (1 - amount) + nodes[current + 1].Value * amount;
                        break;
                    case EMdxInterpolationType.Hermite:
                        ret = nodes[current].Value * (1 - amount) + nodes[current + 1].Value * amount;
                        break;
                    case EMdxInterpolationType.Bezier:
                        ret = nodes[current].Value * (1 - amount) + nodes[current + 1].Value * amount;
                        break;
                }
            }
            else
                ret = 1.0f;

            return ret;
        }
    }

    public class BoneRenderer : NodeRenderer
    {
        private int _GeosetId;

        public BoneRenderer(Bone bone, List<GeosetAnimationRenderer> GeosetAnimationRenderers)
            : base(bone)
        {
            _GeosetId = bone.GeosetId;
        }

        public override void Update(int CurrentTick)
        {
            base.Update(CurrentTick);
        }

        public override void Draw()
        {
            //Vector3 Position = this.PivorPoint;
            //Position = Vector3.Transform(Position, this.World);
            ////Position = Vector3.Transform(Position, Matrix.CreateRotationX(-(float)Math.PI / 2.0f));
            //DebugPrimitiveRenderer.Add(Position , Color.Black);
        }
    }

    public class HelperRenderer : NodeRenderer
    {
        public HelperRenderer(Helper helper)
            : base(helper)
        {
            // emtpy
        }
    }

    public class NodeRenderer
    {
        private Node _Node;

        private Matrix _Translation;
        private Matrix _Rotation;
        private Matrix _Scaling;

        private Matrix _World;

        public Vector3 PivorPoint
        {
            get { return _Node.PivorPoint; }
        }

        public Node Node
        {
            get { return _Node; }
        }
        public int NodeId
        {
            get { return _Node.NodeId; }
            set { _Node.NodeId = value; }
        }
        public int ParentNodeId
        {
            get { return _Node.ParentNodeId; }
        }

        public Matrix World
        {
            set { _World = value; }
            get { return _World; }
        }

        public NodeRenderer(Node node)
        {
            this._Node = node;
        }

        public virtual void Update(int CurrentKeyFrame)
        {
            this._Translation = GetAnimatorMatrix(this._Node.Translation, EAnimateType.Translation, CurrentKeyFrame);
            this._Rotation = GetAnimatorMatrix(this._Node.Rotation, EAnimateType.Rotation, CurrentKeyFrame);
            this._Scaling = GetAnimatorMatrix(this._Node.Scaling, EAnimateType.Scaling, CurrentKeyFrame);

            Vector3 pivor = Vector3.Transform(this._Node.PivorPoint, this._Translation);
            this._World = Matrix.CreateTranslation(-pivor) *
                          this._Rotation * this._Scaling *
                          Matrix.CreateTranslation(pivor);
        }

        public virtual void Draw()
        {
            // empty
        }

        private enum EAnimateType
        {
            Rotation,
            Scaling,
            Translation,
        }

        private Vector3 ToVector3(Vector4 Vector4)
        {
            return new Vector3(Vector4.X, Vector4.Y, Vector4.Z);
        }

        private Matrix GetAnimatorMatrix(Vector4Animator animator, EAnimateType type, int CurrentKeyFrame)
        {
            int length = animator.AnimatorNodes.Length;
            if (length == 0)
                return Matrix.Identity;

            if (CurrentKeyFrame < animator.AnimatorNodes[0].Time || CurrentKeyFrame >= animator.AnimatorNodes[length - 1].Time)
                return Matrix.Identity;

            int index = 0;
            for (index = 0; index < animator.AnimatorNodes.Length; index++)
                if (animator.AnimatorNodes[index].Time <= CurrentKeyFrame && CurrentKeyFrame < animator.AnimatorNodes[index + 1].Time) break;

            Vector4AnimatorNode current = animator.AnimatorNodes[index];
            Vector4AnimatorNode next = animator.AnimatorNodes[index + 1];
            float amount = (float)(CurrentKeyFrame - current.Time) / (float)(next.Time - current.Time);
            Vector4 result = new Vector4();

            animator.Type = EMdxInterpolationType.Linear;
            switch (animator.Type)
            {
                case EMdxInterpolationType.None:
                    result = current.Value;
                    break;
                case EMdxInterpolationType.Linear:
                    result = Vector4.Lerp(current.Value, next.Value, amount);
                    break;
                case EMdxInterpolationType.Hermite:
                    //if (type == EAnimateType.Rotation)
                    //    result = Vector4.Hermite(current.Value, current.OutTangent, next.Value, next.InTangent, amount);
                    //else
                    //    result = new Vector4(Vector3.Hermite(ToVector3(current.Value), ToVector3(current.OutTangent),
                    //                                         ToVector3(next.Value), ToVector3(next.InTangent), amount), 0);
                    if (type == EAnimateType.Rotation)
                        result = MathHelper.InterpolateHermite(current.Value, current.OutTangent, next.Value, next.InTangent, amount);
                    else
                        result = MathHelper.InterpolateHermite(ToVector3(current.Value), ToVector3(current.OutTangent),
                            ToVector3(next.Value), ToVector3(next.InTangent), amount);
                    break;
                case EMdxInterpolationType.Bezier:
                    //if (type == EAnimateType.Rotation)
                    //    result = MathHelper.InterpolateBezier(current.Value, current.OutTangent, next.Value, next.InTangent, amount);
                    //else
                    //    result = MathHelper.InterpolateBezier(ToVector3(current.Value), ToVector3(current.OutTangent), 
                    //        ToVector3(next.Value), ToVector3(next.InTangent), amount);
                    result = Vector4.Lerp(current.Value, next.Value, amount);
                    //throw new Exception("Currently it does not support bezier interpolation.");
                    // use linear instead
                    break;
                default:
                    throw new Exception("Incorrect interpolation type is got.");
            }

            switch (type)
            {
                case EAnimateType.Rotation:
                    return Matrix.CreateFromQuaternion(new Quaternion(result.X, result.Y, result.Z, result.W));
                case EAnimateType.Translation:
                    return Matrix.CreateTranslation(result.X, result.Y, result.Z);
                case EAnimateType.Scaling:
                    return Matrix.CreateScale(result.X, result.Y, result.Z);
                default:
                    throw new Exception("Incorrect animator type is got.");
            }
        }
    }

    public class SequenceAnimationPlayer
    {
        private List<Sequence> _Sequences;

        private int _CurrnetClipIndex;
        private SequencePlayer _SequencePlayer;

        public int CurrentTick
        {
            get { return _SequencePlayer.CurrentTick; }
        }

        public bool IsPlaying
        {
            get { return _SequencePlayer.IsPlaying; }
        }

        public SequenceAnimationPlayer(List<Sequence> sequences)
        {
            _Sequences = sequences;
        }

        public void SetAnimationClip(int clipIndex)
        {
            _CurrnetClipIndex = clipIndex;
            _SequencePlayer = new SequencePlayer(_Sequences[clipIndex]);
        }

        private void Reset()
        {
            if (_SequencePlayer != null)
                _SequencePlayer.Reset();
        }

        public void Update(TimeSpan elapsedTime)
        {
            _SequencePlayer.Update(elapsedTime);
        }

        public void StepForward(int steps)
        {
            _SequencePlayer.StepForward(steps);
        }

        public void Start(float AnimationSpeed)
        {
            _SequencePlayer.Start(AnimationSpeed);
        }

        public void Stop()
        {
            Reset();
        }

        // player next clip
        public void NextClip()
        {
            _CurrnetClipIndex = (_CurrnetClipIndex + 1) % _Sequences.Count;
            SetAnimationClip(_CurrnetClipIndex);

            System.Diagnostics.Debug.WriteLine(_CurrnetClipIndex + _Sequences[_CurrnetClipIndex].Name);
        }

        public int FindName(string name)
        {
            int ret = -1;
            for (int i = 0; i < _Sequences.Count; i++)
                if (name.ToLower().CompareTo(_Sequences[i].Name.ToLower()) == 0)
                {
                    ret = i;
                    break;
                }
            return ret;
        }

        public int GetRandomIndex()
        {
            return XNADotaHelper.GetRandomNumber(_Sequences.Count);
        }

        public Sequence this[int index]
        {
            get
            {
                return _Sequences[index];
            }
        }
    }


    public class AttachmentRenderer : NodeRenderer
    {
        private Attachment _Attachment;

        public AttachmentRenderer(Attachment attachment)
            : base(attachment)
        {
            _Attachment = attachment;
        }

        public override void Update(int CurrentTick)
        {
            base.Update(CurrentTick);
        }

        public override void Draw()
        {
            //Vector3 Position = this.PivorPoint;
            //Position = Vector3.Transform(Position, this.World);
            //Position = Vector3.Transform(Position, Matrix.CreateRotationX(-(float)Math.PI / 2.0f));
            //DebugPrimitiveRenderer.Add(Position, Color.Black);

            base.Draw();
        }
    }

    public class CollisionShapeRenderer : NodeRenderer
    {
        private CollisionShape _CollisionShape;

        public CollisionShapeRenderer(CollisionShape collisionShape)
            : base(collisionShape)
        {
            _CollisionShape = collisionShape;
        }

        public override void Update(int CurrentKeyFrame)
        {
            base.Update(CurrentKeyFrame);
        }

        public override void Draw()
        {
            base.Draw();
        }
    }

    public class LightRenderer : NodeRenderer
    {
        private Light _Light;

        public LightRenderer(Light light)
            : base(light)
        {
            _Light = light;
        }
    }

    public class ParticleEmitterRenderer : NodeRenderer
    {
        private ParticleEmitter _ParticleEmitter;

        public ParticleEmitterRenderer(ParticleEmitter particleEmitter)
            : base(particleEmitter)
        {
            _ParticleEmitter = particleEmitter;
        }
    }

    public class ParticleEmitter2Renderer : NodeRenderer
    {
        private ParticleEmitter2 _ParticleEmitter2;

        public ParticleEmitter2Renderer(ParticleEmitter2 particleEmitter2)
            : base(particleEmitter2)
        {
            _ParticleEmitter2 = particleEmitter2;
        }

        public override void Draw()
        {
        }
    }

    public class RibbonEmitterRenderer : NodeRenderer
    {
        private RibbonEmitter _RibbonEmitter;

        public RibbonEmitterRenderer(RibbonEmitter ribbonEmitter)
            : base(ribbonEmitter)
        {
            _RibbonEmitter = ribbonEmitter;
        }

        public override void Draw()
        {
            //DebugPrimitiveRenderer.AddPermanentPrimitive(this.PivorPoint, Color.Red);

            base.Draw();
        }
    }

    public class EventObjectRenderer : NodeRenderer
    {
        private EventObject _EventObject;

        public EventObjectRenderer(EventObject eventObject)
            : base(eventObject)
        {
            _EventObject = eventObject;
        }
    }

    public class CameraRenderer
    {
        private Camera _Camera;

        public CameraRenderer(Camera camera)
        {
            _Camera = camera;
        }
    }

    public class CollisionShapeDectector
    {
        private List<CollisionShape> _CollisionShapes;

        public CollisionShapeDectector(List<CollisionShape> collisionShapes)
        {
            _CollisionShapes = collisionShapes;
        }
    }

    public static class MathHelper
    {
        public static Vector4 InterpolateBezier(Vector4 value1, Vector4 outTangent, Vector4 value2, Vector4 inTangent, float Factor)
        {
            float FactorX2 = Factor * Factor;
            float InverseFactor = 1.0f - Factor;
            float InverseFactorX2 = InverseFactor * InverseFactor;

            float Factor1 = InverseFactorX2 * InverseFactor;
            float Factor2 = 3.0f * Factor * InverseFactorX2;
            float Factor3 = 3.0f * FactorX2 * InverseFactor;
            float Factor4 = FactorX2 * Factor;

            return (value1 * Factor1) + (outTangent * Factor2) + (inTangent * Factor3) + (value2 * Factor4);
        }

        public static Vector3 InterpolateBezier(Vector3 value1, Vector3 outTangent, Vector3 value2, Vector3 inTangent, float Factor)
        {
            float FactorX2 = Factor * Factor;
            float InverseFactor = 1.0f - Factor;
            float InverseFactorX2 = InverseFactor * InverseFactor;

            float Factor1 = InverseFactorX2 * InverseFactor;
            float Factor2 = 3.0f * Factor * InverseFactorX2;
            float Factor3 = 3.0f * FactorX2 * InverseFactor;
            float Factor4 = FactorX2 * Factor;

            return (value1 * Factor1) + (outTangent * Factor2) + (inTangent * Factor3) + (value2 * Factor4);
        }

        public static Vector4 InterpolateHermite(Vector4 value1, Vector4 outTangent, Vector4 value2, Vector4 inTangent, float Factor)
        {
            //float Factor = (float)(Time.Time - Node1.Time) / (float)(Node2.Time - Node1.Time);
            float FactorX2 = Factor * Factor;

            float Factor1 = FactorX2 * (2.0f * Factor - 3.0f) + 1;
            float Factor2 = FactorX2 * (Factor - 2.0f) + Factor;
            float Factor3 = FactorX2 * (Factor - 1.0f);
            float Factor4 = FactorX2 * (3.0f - 2.0f * Factor);

            float X = (value1.X * Factor1) + (outTangent.X * Factor2) + (inTangent.X * Factor3) + (value2.X * Factor4);
            float Y = (value1.Y * Factor1) + (outTangent.Y * Factor2) + (inTangent.Y * Factor3) + (value2.Y * Factor4);
            float Z = (value1.Z * Factor1) + (outTangent.Z * Factor2) + (inTangent.Z * Factor3) + (value2.Z * Factor4);
            float W = (value1.W * Factor1) + (outTangent.W * Factor2) + (inTangent.W * Factor3) + (value2.W * Factor4);

            return new Vector4(X, Y, Z, W);
        }

        public static Vector4 InterpolateHermite(Vector3 value1, Vector3 outTangent, Vector3 value2, Vector3 inTangent, float Factor)
        {
            //float Factor = (float)(Time.Time - Node1.Time) / (float)(Node2.Time - Node1.Time);
            float FactorX2 = Factor * Factor;

            float Factor1 = FactorX2 * (2.0f * Factor - 3.0f) + 1;
            float Factor2 = FactorX2 * (Factor - 2.0f) + Factor;
            float Factor3 = FactorX2 * (Factor - 1.0f);
            float Factor4 = FactorX2 * (3.0f - 2.0f * Factor);

            float X = (value1.X * Factor1) + (outTangent.X * Factor2) + (inTangent.X * Factor3) + (value2.X * Factor4);
            float Y = (value1.Y * Factor1) + (outTangent.Y * Factor2) + (inTangent.Y * Factor3) + (value2.Y * Factor4);
            float Z = (value1.Z * Factor1) + (outTangent.Z * Factor2) + (inTangent.Z * Factor3) + (value2.Z * Factor4);

            return new Vector4(X, Y, Z, 0);
        }
    }
}
